use codegraph_core::{
    GraphExporter, ExportFormat, ExportConfig, LayoutHints, NodeType, EdgeType,
    SourceLocation, NodeMetadata, EdgeMetadata,
};
use std::collections::HashMap;
use uuid::Uuid;

// Mock implementations for testing
struct MockNode {
    id: Uuid,
    node_type: NodeType,
    name: String,
    location: SourceLocation,
    metadata: NodeMetadata,
}

impl codegraph_core::CodeNode for MockNode {
    fn id(&self) -> codegraph_core::NodeId {
        self.id
    }

    fn node_type(&self) -> &NodeType {
        &self.node_type
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn location(&self) -> &SourceLocation {
        &self.location
    }

    fn language(&self) -> &str {
        "python"
    }

    fn metadata(&self) -> &NodeMetadata {
        &self.metadata
    }

    fn edges(&self, _edge_type: Option<&EdgeType>) -> Vec<&dyn codegraph_core::CodeEdge> {
        vec![]
    }

    fn add_edge(&mut self, _edge: Box<dyn codegraph_core::CodeEdge>) -> codegraph_core::Result<()> {
        Ok(())
    }

    fn remove_edge(&mut self, _id: codegraph_core::EdgeId) -> codegraph_core::Result<bool> {
        Ok(false)
    }

    fn update_metadata(&mut self, metadata: NodeMetadata) -> codegraph_core::Result<()> {
        self.metadata = metadata;
        Ok(())
    }
}

struct MockEdge {
    id: Uuid,
    source: Uuid,
    target: Uuid,
    edge_type: EdgeType,
    metadata: EdgeMetadata,
}

impl codegraph_core::CodeEdge for MockEdge {
    fn id(&self) -> codegraph_core::EdgeId {
        self.id
    }

    fn source(&self) -> codegraph_core::NodeId {
        self.source
    }

    fn target(&self) -> codegraph_core::NodeId {
        self.target
    }

    fn edge_type(&self) -> &EdgeType {
        &self.edge_type
    }

    fn metadata(&self) -> &EdgeMetadata {
        &self.metadata
    }

    fn update_metadata(&mut self, metadata: EdgeMetadata) -> codegraph_core::Result<()> {
        self.metadata = metadata;
        Ok(())
    }
}

fn create_test_nodes() -> Vec<MockNode> {
    vec![
        MockNode {
            id: Uuid::new_v4(),
            node_type: NodeType::Function,
            name: "main".to_string(),
            location: SourceLocation {
                file_path: "main.py".to_string(),
                start_line: 1,
                start_column: 1,
                end_line: 10,
                end_column: 1,
                byte_offset: 0,
                byte_length: 100,
            },
            metadata: NodeMetadata {
                visibility: Some("public".to_string()),
                modifiers: Vec::new(),
                documentation: Some("Main function".to_string()),
                annotations: Vec::new(),
                complexity: Some(5),
                custom: HashMap::new(),
            },
        },
        MockNode {
            id: Uuid::new_v4(),
            node_type: NodeType::Class,
            name: "User".to_string(),
            location: SourceLocation {
                file_path: "user.py".to_string(),
                start_line: 1,
                start_column: 1,
                end_line: 20,
                end_column: 1,
                byte_offset: 100,
                byte_length: 200,
            },
            metadata: NodeMetadata {
                visibility: Some("public".to_string()),
                modifiers: vec!["abstract".to_string()],
                documentation: Some("User class".to_string()),
                annotations: vec!["@dataclass".to_string()],
                complexity: Some(10),
                custom: HashMap::new(),
            },
        },
        MockNode {
            id: Uuid::new_v4(),
            node_type: NodeType::Variable,
            name: "config".to_string(),
            location: SourceLocation {
                file_path: "config.py".to_string(),
                start_line: 5,
                start_column: 1,
                end_line: 5,
                end_column: 10,
                byte_offset: 300,
                byte_length: 10,
            },
            metadata: NodeMetadata {
                visibility: Some("private".to_string()),
                modifiers: Vec::new(),
                documentation: None,
                annotations: Vec::new(),
                complexity: Some(1),
                custom: HashMap::new(),
            },
        },
    ]
}

fn create_test_edges(nodes: &[MockNode]) -> Vec<MockEdge> {
    if nodes.len() < 2 {
        return vec![];
    }

    vec![
        MockEdge {
            id: Uuid::new_v4(),
            source: nodes[0].id,
            target: nodes[1].id,
            edge_type: EdgeType::Calls,
            metadata: EdgeMetadata {
                weight: 1.0,
                confidence: 0.9,
                context: Some("Function call".to_string()),
                custom: HashMap::new(),
            },
        },
        MockEdge {
            id: Uuid::new_v4(),
            source: nodes[1].id,
            target: nodes[2].id,
            edge_type: EdgeType::References,
            metadata: EdgeMetadata {
                weight: 0.5,
                confidence: 0.8,
                context: Some("Variable reference".to_string()),
                custom: HashMap::new(),
            },
        },
    ]
}

#[tokio::test]
async fn test_graphviz_export() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let config = ExportConfig {
        format: ExportFormat::Graphviz,
        include_metadata: true,
        include_weights: true,
        node_type_filter: None,
        edge_type_filter: None,
        max_nodes: None,
        layout_hints: LayoutHints::default(),
    };

    let exporter = GraphExporter::with_config(config);
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

    println!("Graphviz export test:");
    println!("  Content type: {}", result.content_type);
    println!("  File extension: {}", result.file_extension);
    println!("  Output size: {} bytes", result.stats.output_size_bytes);
    println!("  Nodes exported: {}", result.stats.nodes_exported);
    println!("  Edges exported: {}", result.stats.edges_exported);
    println!("  Export time: {}ms", result.stats.export_time_ms);

    assert_eq!(result.content_type, "text/vnd.graphviz");
    assert_eq!(result.file_extension, "dot");
    assert_eq!(result.stats.nodes_exported, 3);
    assert_eq!(result.stats.edges_exported, 2);
    assert!(result.content.contains("digraph CodeGraph"));
    assert!(result.content.contains("main"));
    assert!(result.content.contains("User"));
    assert!(result.content.contains("config"));
}

#[tokio::test]
async fn test_d3_json_export() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let config = ExportConfig {
        format: ExportFormat::D3Json,
        include_metadata: true,
        include_weights: true,
        node_type_filter: None,
        edge_type_filter: None,
        max_nodes: None,
        layout_hints: LayoutHints::default(),
    };

    let exporter = GraphExporter::with_config(config);
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

    println!("D3 JSON export test:");
    println!("  Content type: {}", result.content_type);
    println!("  File extension: {}", result.file_extension);
    println!("  Output size: {} bytes", result.stats.output_size_bytes);

    assert_eq!(result.content_type, "application/json");
    assert_eq!(result.file_extension, "json");
    assert_eq!(result.stats.nodes_exported, 3);
    assert_eq!(result.stats.edges_exported, 2);

    // Parse JSON to verify structure
    let json: serde_json::Value = serde_json::from_str(&result.content).expect("Invalid JSON");
    assert!(json["nodes"].is_array());
    assert!(json["links"].is_array());
    assert!(json["metadata"].is_object());
    assert_eq!(json["nodes"].as_array().unwrap().len(), 3);
    assert_eq!(json["links"].as_array().unwrap().len(), 2);
}

#[tokio::test]
async fn test_cytoscape_export() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let config = ExportConfig {
        format: ExportFormat::Cytoscape,
        include_metadata: false,
        include_weights: false,
        node_type_filter: None,
        edge_type_filter: None,
        max_nodes: None,
        layout_hints: LayoutHints::default(),
    };

    let exporter = GraphExporter::with_config(config);
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

    println!("Cytoscape export test:");
    println!("  Content type: {}", result.content_type);
    println!("  File extension: {}", result.file_extension);

    assert_eq!(result.content_type, "application/json");
    assert_eq!(result.file_extension, "cyjs");
    assert_eq!(result.stats.nodes_exported, 3);
    assert_eq!(result.stats.edges_exported, 2);

    // Parse JSON to verify structure
    let json: serde_json::Value = serde_json::from_str(&result.content).expect("Invalid JSON");
    assert!(json["elements"].is_array());
    assert!(json["style"].is_array());
    assert!(json["layout"].is_object());
}

#[tokio::test]
async fn test_export_with_filters() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    // Filter to only include functions
    let config = ExportConfig {
        format: ExportFormat::Graphviz,
        include_metadata: true,
        include_weights: true,
        node_type_filter: Some(vec![NodeType::Function]),
        edge_type_filter: Some(vec![EdgeType::Calls]),
        max_nodes: None,
        layout_hints: LayoutHints::default(),
    };

    let exporter = GraphExporter::with_config(config);
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

    println!("Filtered export test:");
    println!("  Nodes exported: {}", result.stats.nodes_exported);
    println!("  Edges exported: {}", result.stats.edges_exported);

    // Should only export function nodes
    assert_eq!(result.stats.nodes_exported, 1);
    // Should only export edges between remaining nodes
    assert_eq!(result.stats.edges_exported, 0); // No calls edges between function and itself
    assert!(result.content.contains("main"));
    assert!(!result.content.contains("User"));
    assert!(!result.content.contains("config"));
}

#[tokio::test]
async fn test_export_with_node_limit() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let config = ExportConfig {
        format: ExportFormat::Graphviz,
        include_metadata: true,
        include_weights: true,
        node_type_filter: None,
        edge_type_filter: None,
        max_nodes: Some(2), // Limit to 2 nodes
        layout_hints: LayoutHints::default(),
    };

    let exporter = GraphExporter::with_config(config);
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

    println!("Node limit export test:");
    println!("  Nodes exported: {}", result.stats.nodes_exported);
    println!("  Edges exported: {}", result.stats.edges_exported);

    assert_eq!(result.stats.nodes_exported, 2);
    // Edges should be filtered to only include those between remaining nodes
    assert!(result.stats.edges_exported <= 2);
}

#[tokio::test]
async fn test_all_export_formats() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let formats = vec![
        (ExportFormat::Graphviz, "text/vnd.graphviz", "dot"),
        (ExportFormat::D3Json, "application/json", "json"),
        (ExportFormat::Cytoscape, "application/json", "cyjs"),
        (ExportFormat::Gexf, "application/xml", "gexf"),
        (ExportFormat::GraphML, "application/xml", "graphml"),
    ];

    for (format, expected_content_type, expected_extension) in formats {
        let config = ExportConfig {
            format,
            include_metadata: true,
            include_weights: true,
            node_type_filter: None,
            edge_type_filter: None,
            max_nodes: None,
            layout_hints: LayoutHints::default(),
        };

        let exporter = GraphExporter::with_config(config);
        let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");

        println!("Format {:?} test:", format);
        println!("  Content type: {}", result.content_type);
        println!("  File extension: {}", result.file_extension);
        println!("  Output size: {} bytes", result.stats.output_size_bytes);

        assert_eq!(result.content_type, expected_content_type);
        assert_eq!(result.file_extension, expected_extension);
        assert_eq!(result.stats.nodes_exported, 3);
        assert_eq!(result.stats.edges_exported, 2);
        assert!(result.stats.output_size_bytes > 0);
        // Export time should be recorded
        assert!(result.stats.export_time_ms < 10000); // Should be reasonable
    }
}

#[tokio::test]
async fn test_export_performance() {
    let nodes = create_test_nodes();
    let edges = create_test_edges(&nodes);
    
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n as &dyn codegraph_core::CodeNode).collect();
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e as &dyn codegraph_core::CodeEdge).collect();

    let exporter = GraphExporter::new();
    
    let start_time = std::time::Instant::now();
    let result = exporter.export_graph(&node_refs, &edge_refs).await.expect("Export failed");
    let total_time = start_time.elapsed();

    println!("Export performance test:");
    println!("  Total time: {:?}", total_time);
    println!("  Reported export time: {}ms", result.stats.export_time_ms);
    println!("  Nodes per ms: {:.2}", result.stats.nodes_exported as f64 / result.stats.export_time_ms as f64);

    // Performance should be reasonable
    assert!(result.stats.export_time_ms < 1000); // Should complete in under 1 second
    assert!(total_time.as_millis() < 1000);
}
